notes

#tips and tricks

๐Ÿซ  Hydration Mismatch ๐Ÿซ  (1)2023. 6. 2.

์ด๋Ÿฐ ์ €๋Ÿฐ ์ด์œ ๋กœ ํด๋ผ์ด์–ธํŠธ ์ƒํƒœ๋ฅผ localStorage ๋“ฑ์— persist ํ•˜๊ณ  ์žˆ์„ ๊ฒฝ์šฐ server/client mismatch๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ๋ฐ–์— ์—†๋Š”๋ฐ ๊ทธ๋ž˜์„œ ์„œ๋ฒ„์—์„œ ํ”„๋ฆฌ๋ Œ๋”๊ฐ€ ์•ˆ๋˜๊ฒŒ ํ•˜๋ ค๋ฉด ์•„๋ž˜์™€ ๊ฐ™์€ ๋‚œ๋ฆฌ ๋ฒ•์„์ด ํ•„์š”.

  1. useState + useEffect
    function Comp() {
      const storeState = useStoreState()
            ^^^^^^^^^^ 1) ์š”๊ฑธ ๊ทธ๋ƒฅ ์“ฐ๋ฉด ๐Ÿ’ฃ ์ธ ๊ฒฝ์šฐ,
    
      const [state, setState] = React.useState()
      React.useEffect(()=>{
        setState(storeState)
      },[])               // 2) ์ด๋Ÿฐ ๋‚œ๋ฆฌ ๋ฒ•์„ ํ›„์—
    
      return <div>{state}</div> // 3) ์ด๋Ÿฌ๋ฉด ํ†ต๊ณผ
    function Comp() {
      const storeState = useStoreState()
            ^^^^^^^^^^ 1) ์š”๊ฑธ ๊ทธ๋ƒฅ ์“ฐ๋ฉด ๐Ÿ’ฃ ์ธ ๊ฒฝ์šฐ,
    
      const [state, setState] = React.useState()
      React.useEffect(()=>{
        setState(storeState)
      },[])               // 2) ์ด๋Ÿฐ ๋‚œ๋ฆฌ ๋ฒ•์„ ํ›„์—
    
      return <div>{state}</div> // 3) ์ด๋Ÿฌ๋ฉด ํ†ต๊ณผ
  2. useMounted + return null
    // 0) ์ผ๋‹จ ์ด๋Ÿฐ ๋‚œ๋ฆฌ ๋ฒ•์„์„ ๋งŒ๋“ค์–ด๋‘๊ณ 
    const useMounted = () => {
      const [m, sM] = React.useState(false)
      //     ^^^^^ ๊ท€์ฐฎ์•„์„œ ๋Œ€์ถฉ ์”€
      React.useEffect(()=>{
        sM(true)
      }, [])
      return m
    }
    // 0) ์ผ๋‹จ ์ด๋Ÿฐ ๋‚œ๋ฆฌ ๋ฒ•์„์„ ๋งŒ๋“ค์–ด๋‘๊ณ 
    const useMounted = () => {
      const [m, sM] = React.useState(false)
      //     ^^^^^ ๊ท€์ฐฎ์•„์„œ ๋Œ€์ถฉ ์”€
      React.useEffect(()=>{
        sM(true)
      }, [])
      return m
    }
    function Comp() {
      const storeState = useStoreState()
            ^^^^^^^^^^ 1) ์š”๊ฑธ ๊ทธ๋ƒฅ ์“ฐ๋ฉด ๐Ÿ’ฃ ์ธ ๊ฒฝ์šฐ,
    
      const mounted = useMounted()
      if (!mounted) return null  // 2) ์ด๋Ÿฌ๊ณ  ๋‚˜์„œ
    
      return <div>{storeState}</div> // 3) ์ด๋Ÿฌ๋ฉด ํ†ต๊ณผ
    function Comp() {
      const storeState = useStoreState()
            ^^^^^^^^^^ 1) ์š”๊ฑธ ๊ทธ๋ƒฅ ์“ฐ๋ฉด ๐Ÿ’ฃ ์ธ ๊ฒฝ์šฐ,
    
      const mounted = useMounted()
      if (!mounted) return null  // 2) ์ด๋Ÿฌ๊ณ  ๋‚˜์„œ
    
      return <div>{storeState}</div> // 3) ์ด๋Ÿฌ๋ฉด ํ†ต๊ณผ
  3. next/dynamic + { ssr: false } โ˜œ ์ด๊ฒŒ ๊ธฐ๋ถ„์ด ์ œ์ผ ๋œ ๋‚˜์œ๋“ฏ
    const Comp = dynamic(() => import('path/to/comp'), { ssr: false }); // ์ ค ๊ฐ„๋‹จ?
    const Comp = dynamic(() => import('path/to/comp'), { ssr: false }); // ์ ค ๊ฐ„๋‹จ?
    ํ•˜์ง€๋งŒ ์บ์น˜๊ฐ€ ํ•˜๋‚˜ ์žˆ๋Š”๋ฐ, Comp ๋Š” ๋ฌด์กฐ๊ฑด export default ์—ฌ์•ผ ํ•จ.
    const Comp = dynamic(() => import('path/to/comp').then(mod => mod.Comp), { ssr: false })
                                                        // ^^^^^^^^^^^^^^^ ์ด๋Ÿฌ๋ฉด ๐Ÿ’ฃ
    const Comp = dynamic(() => import('path/to/comp').then(mod => mod.Comp), { ssr: false })
                                                        // ^^^^^^^^^^^^^^^ ์ด๋Ÿฌ๋ฉด ๐Ÿ’ฃ
    ๋Œ€์‹  loading์œผ๋กœ ์„œ์ŠคํŽœ์Šค ๊ฐ„์ง€๋ฅผ ๋‚ผ ์ˆ˜ ์žˆ์Œ(...)
    const Comp = dynamic(() => import('path/to/comp'), { ssr: false, loading: () => <Sekeleton /> }); // ์ด ๊ฐ€๋Šฅ
    const Comp = dynamic(() => import('path/to/comp'), { ssr: false, loading: () => <Sekeleton /> }); // ์ด ๊ฐ€๋Šฅ
    ๋ฌผ๋ก  next ํ•œ์ •์ด์ง€๋งŒ์š”...

์ด๊ฑฐ ๋‹ค ๋ณ„๋ฃจ๊ณ ... ๋กœ ์‹œ์ž‘ํ•˜๋Š” ๊ธ€์„ ํ•œ์ฐธ ์“ฐ๊ณ  ์žˆ์—ˆ๋Š”๋ฐ ๋ธŒ๋ผ์šฐ์ € ๊บผ์ ธ์„œ ๋‚ ์•„๊ฐ...

`@vercel/og` Cheatsheet (?)2023. 5. 24.

  1. @vercel/og
  2. Edge Runtime ํ™˜๊ฒฝ ๊ธฐ๋ฐ˜์œผ๋กœ ๋™์ž‘ํ•จ.
  3. ์ด๋ฏธ์ง€ ๋ Œ๋”์— satori๋ฅผ ์‚ฌ์šฉํ•˜๋Š”๋ฐ,
  4. app router ์‚ฌ์šฉ์‹œ app/og/route.tsx ํ˜น์€ app/og.tsx ๋“ฑ๋“ฑ์œผ๋กœ ํŒŒ์ผ์„ ๋งŒ๋“ค๋ฉด ๋จ.
    • .ts๋„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์œผ๋‚˜ ๊ณ ๋Ÿฌ๋ฉด jsx๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์—†๊ฒŸ์ฅฌ
  5. ์ปค์Šคํ…€ ํฐํŠธ๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์œผ๋‚˜ next/font ์™ธ์— ๋ณ„๋„๋กœ ๋กœ์ปฌ์—์„œ ํฐํŠธ ํŒŒ์ผ์„ ๊ฐ€์ ธ์™€์•ผ ํ•จ. (๋ฆฌ๋ชจํŠธ๋Š” ์•„์ง ์•ˆํ•ด๋ด„)
    • .ttf, .woff ์‚ฌ์šฉ ๊ฐ€๋Šฅ (.woff2๋Š” ์•ˆ๋จ)
  6. ๋Œ€๋žต์˜ api๋Š” ์•„๋ž˜์™€ ๊ฐ™์Œ.
    new ImageResponse(
      element: ReactElement,
      options: {
        width?: number = 1200
        height?: number = 630
        emoji?: 'twemoji' | 'blobmoji' | 'noto' | 'openmoji' = 'twemoji', // emoji render์— ์–ด๋–ค lib์„ ์‚ฌ์šฉํ•  ๊ฒƒ์ธ์ง€
        fonts?: {
          name: string,
          data: ArrayBuffer, // ํฐํŠธ ํŒŒ์ผ ๋ฐ์ดํ„ฐ. fetch(URL).then(res => res.imageBuffer())๋กœ ๊ฐ€์ ธ์˜ค๋ฉด ๋œ๋‹ค.
          weight: number,
          style: 'normal' | 'italic'
        }[]
        debug?: boolean = false // true ์ผ ๊ฒฝ์šฐ ๊ฐ element์˜ border, line-height ๋“ฑ์ด ํ‘œ์‹œ๋จ. 
    
        status?: number = 200
        statusText?: string
        headers?: Record<string, string>
      },
    )
    new ImageResponse(
      element: ReactElement,
      options: {
        width?: number = 1200
        height?: number = 630
        emoji?: 'twemoji' | 'blobmoji' | 'noto' | 'openmoji' = 'twemoji', // emoji render์— ์–ด๋–ค lib์„ ์‚ฌ์šฉํ•  ๊ฒƒ์ธ์ง€
        fonts?: {
          name: string,
          data: ArrayBuffer, // ํฐํŠธ ํŒŒ์ผ ๋ฐ์ดํ„ฐ. fetch(URL).then(res => res.imageBuffer())๋กœ ๊ฐ€์ ธ์˜ค๋ฉด ๋œ๋‹ค.
          weight: number,
          style: 'normal' | 'italic'
        }[]
        debug?: boolean = false // true ์ผ ๊ฒฝ์šฐ ๊ฐ element์˜ border, line-height ๋“ฑ์ด ํ‘œ์‹œ๋จ. 
    
        status?: number = 200
        statusText?: string
        headers?: Record<string, string>
      },
    )
  7. ๋Œ€๋žต์˜ ์‚ฌ์šฉ๋ก€๋Š” ์•„๋ž˜์™€ ๊ฐ™์Œ.
    // app/og/route.tsx
    import { ImageResponse } from 'next/server'; // app router ์‚ฌ์šฉ์‹œ @verce/og๊ฐ€ ํฌํ•จ๋˜์–ด ์žˆ์Œ
    
    export const runtime = 'edge';
    
    const font = fetch(new URL('../path/to/font/Font.woff', import.meta.url)).then(
      (res) => res.arrayBuffer(),
    );
    
    export async function GET(request: Request) {
      const fontData = await font;
    
      // query param์œผ๋กœ ์ด๋Ÿฐ ์ €๋Ÿฐ ํ…์ŠคํŠธ๋ฅผ ๋™์ ์œผ๋กœ ๋„ฃ์„ ์ˆ˜ ์žˆ์Œ.
      const url = new URL(request.url)
      const searchParams = url.searchParams
      const title = searchParams.has("title") ? searchParams.get("title") : null
    
      return new ImageResponse(
        (
          <div
            style={{
            backgroundColor: 'white',
            height: '100%',
            width: '100%',
            fontSize: 100,
            fontFamily: '"Font"',
            paddingTop: '100px',
            paddingLeft: '50px',
          }}
         >
           {title ? title : 'Hello World!'}
         </div>
       ),
       {
         width: 1200,
         height: 630,
         fonts: [
           {
             name: 'Font',
             data: fontData,
             style: 'normal',
           },
         ],
       },
     );
    }
    // app/og/route.tsx
    import { ImageResponse } from 'next/server'; // app router ์‚ฌ์šฉ์‹œ @verce/og๊ฐ€ ํฌํ•จ๋˜์–ด ์žˆ์Œ
    
    export const runtime = 'edge';
    
    const font = fetch(new URL('../path/to/font/Font.woff', import.meta.url)).then(
      (res) => res.arrayBuffer(),
    );
    
    export async function GET(request: Request) {
      const fontData = await font;
    
      // query param์œผ๋กœ ์ด๋Ÿฐ ์ €๋Ÿฐ ํ…์ŠคํŠธ๋ฅผ ๋™์ ์œผ๋กœ ๋„ฃ์„ ์ˆ˜ ์žˆ์Œ.
      const url = new URL(request.url)
      const searchParams = url.searchParams
      const title = searchParams.has("title") ? searchParams.get("title") : null
    
      return new ImageResponse(
        (
          <div
            style={{
            backgroundColor: 'white',
            height: '100%',
            width: '100%',
            fontSize: 100,
            fontFamily: '"Font"',
            paddingTop: '100px',
            paddingLeft: '50px',
          }}
         >
           {title ? title : 'Hello World!'}
         </div>
       ),
       {
         width: 1200,
         height: 630,
         fonts: [
           {
             name: 'Font',
             data: fontData,
             style: 'normal',
           },
         ],
       },
     );
    }
  8. tailwind ์‚ฌ์šฉ์ด ๊ฐ€๋Šฅํ•œ๋ฐ ์•„์ง experimental์ด ๋ถ™์–ด์žˆ๊ณ  className ๋ง๊ณ  tw๋ฅผ ์‚ฌ์šฉํ•˜๋„๋ก ๋˜์–ด์žˆ์Œ.
  9. Hobby plan์ผ ๊ฒฝ์šฐ ๋‹จ์ผ function๋‹น 1MB ์ œํ•œ์ด ์žˆ์–ด ํ•œ๊ธ€ ์ปค์Šคํ…€ ํฐํŠธ๋ฅผ ์ถ”๊ฐ€ํ•˜๊ธด ์‰ฝ์ง€ ์•Š์•˜์Œ.
  10. sehyunchung.dev์— ์ ์šฉํ•ด๋ณธ ๊ฒฐ๊ณผ -> https://sehyunchung.dev/og?title=์•”์˜จ๋”๋„ง๋ ™์„&description=์ ˆ๋Œ€์ ๋ฃฐ์„์ง€์ผœ

Changing Learning Environment IS Beneficial2023. 5. 22.

Q: How important is routine when it comes to learning? For example, is it important to have a dedicated study area?

A: Not at all. Most people do better over time by varying their study or practice locations. The more environments in which you rehearse, the sharper and more lasting the memory of that material becomesโ€”and less strongly linked to one โ€œcomfort zone.โ€ That is, knowledge becomes increasingly independent of surroundings the more changes you makeโ€”taking your laptop onto the porch, out to a cafรฉ, on the plane. The goal, after all, is to be able to perform well in any conditions. Changing locations is not the only way to take advantage of the so-called context effect on learning, however. Altering the time of day you study also helps, as does changing how you engage the material, by reading or discussing, typing into a computer or writing by hand, reciting in front of a mirror or studying while listening to music: Each counts as a different learning โ€œenvironmentโ€ in which you store the material in a different way.

Benedict Carey, How We Learn. Random House Trade Paperbacks; Reprint edition (June 9, 2015) p. 201

Split string into array2023. 5. 18.

words="hello hello how low?"

array=($words);

for word in "${array[@]}"; do
  echo $word
done
words="hello hello how low?"

array=($words);

for word in "${array[@]}"; do
  echo $word
done
hello
hello
how
low?
hello
hello
how
low?

Tags